﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Toenda.Foundation.Checksum;

namespace Toenda.Foundation.Types {
	/// <summary>
	/// The ISIN struct represents a International Securities Identification Number
	/// </summary>
	public struct ISIN {
		private string _countryPart;
		private string _nsinPart;
		private int? _checkNumber;

		/// <summary>
		/// Creates a new object of <see cref="ISIN"/>
		/// </summary>
		/// <param name="isin">A string that contains a ISIN.</param>
		public ISIN(string isin) {
			ISIN obj = ISIN.Parse(isin);

			this._countryPart = obj.CountryPart;
			this._nsinPart = obj.NsinPart;
			this._checkNumber = obj.CheckNumber;
		}

		/// <summary>
		/// Creates a new object of <see cref="ISIN"/>
		/// </summary>
		/// <param name="countryPart">The country part of a ISIN.</param>
		/// <param name="nsinPart">The NSIN part of a ISIN.</param>
		public ISIN(string countryPart, string nsinPart) {
			this._countryPart = countryPart;
			this._nsinPart = nsinPart;
			this._checkNumber = ComputeChecksum(countryPart + nsinPart);
		}

		/// <summary>
		/// Creates a new object of <see cref="ISIN"/>
		/// </summary>
		/// <param name="countryPart">The country part of a ISIN.</param>
		/// <param name="nsinPart">The NSIN part of a ISIN.</param>
		/// <param name="checkNumber">The checknumber of the ISIN.</param>
		public ISIN(string countryPart, string nsinPart, int checkNumber) {
			this._countryPart = countryPart;
			this._nsinPart = nsinPart;
			this._checkNumber = checkNumber;
		}

		/// <summary>
		/// Get or set the country part
		/// </summary>
		public string CountryPart {
			get { return this._countryPart; }
			set { this._countryPart = value; }
		}

		/// <summary>
		/// Get or set the NSIN part
		/// </summary>
		public string NsinPart {
			get { return this._nsinPart; }
			set { this._nsinPart = value; }
		}

		/// <summary>
		/// Get or set the check number
		/// </summary>
		public int? CheckNumber {
			get { return this._checkNumber; }
			set {
				this._checkNumber = value;

				if(!this._countryPart.IsNullOrTrimmedEmpty()
				&& !this._nsinPart.IsNullOrTrimmedEmpty()) {
					int checkNumber = ComputeChecksum(this._countryPart + this._nsinPart);

					if(this._checkNumber != checkNumber) {
						this._checkNumber = checkNumber;
					}
				}
			}
		}

		/// <summary>
		/// Provides a value that indicates if the ISIN is null
		/// </summary>
		public bool IsNull {
			get {
				if(this.CountryPart.IsNullOrTrimmedEmpty()
				&& this.NsinPart.IsNullOrTrimmedEmpty()) {
					return true;
				}

				return false;
			}
		}

		/// <summary>
		/// Returns a string that represents a ISIN number
		/// </summary>
		/// <returns>Returns a string that represents a ISIN number.</returns>
		public override string ToString() {
			StringBuilder str = new StringBuilder();

			str.Append(this._countryPart);
			str.Append(this._nsinPart);

			if(this._checkNumber.HasValue) {
				str.Append(this._checkNumber);
			}

			return str.ToString();
		}

		/// <summary>
		/// Returns the hashcode of the object
		/// </summary>
		/// <returns></returns>
		public override int GetHashCode() {
			return base.GetHashCode();
		}

		/// <summary>
		/// Converts implizit a string containing a ISIN to a ISIN object
		/// </summary>
		/// <param name="isin">A string that contains a ISIN.</param>
		/// <returns>Returns a ISIN object on base of a string containing a ISIN.</returns>
		public static implicit operator ISIN(string isin) {
			if(isin == null) {
				return new ISIN(null, null);
			}

			return ISIN.Parse(isin);
		}

		/// <summary>
		/// Converts implizit a ISIN object to a string
		/// </summary>
		/// <param name="isin">The ISIN object to be converted.</param>
		/// <returns>Returns a string containing a ISIN.</returns>
		public static implicit operator string(ISIN isin) {
			if(isin == null 
			|| (isin.CountryPart == null && isin.NsinPart == null)) {
				return null;
			}

			return isin.ToString();
		}

		/// <summary>
		/// Checks if a object equals the current object
		/// </summary>
		/// <param name="obj">Another ISIN object.</param>
		/// <returns>Returns a value indicating if the parameter object equals the current object.</returns>
		public override bool Equals(object obj) {
			try {
				if(obj == null) {
					return false;
				}

				ISIN isin = (ISIN)obj;

				if(this.CountryPart == isin.CountryPart
				&& this.NsinPart == isin.NsinPart
				&& this.CheckNumber == isin.CheckNumber) {
					return true;
				}
				else {
					return false;
				}
			}
			catch(Exception) {
				return false;
			}
		}

		/// <summary>
		/// Checks if the first ISIN object equals the second ISIN object
		/// </summary>
		/// <param name="isin1">The first ISIN object.</param>
		/// <param name="isin2">The second ISIN object.</param>
		/// <returns>Returns a value indicating if the first ISIN object equals the second ISIN object.</returns>
		public static bool operator ==(ISIN isin1, ISIN isin2) {
			try {
				if(object.ReferenceEquals(isin1, isin2)) {
					return true;
				}

				if(((object)isin1 != null) && ((object)isin2 == null)) {
					return false;
				}

				if(((object)isin1 == null) && ((object)isin2 != null)) {
					return false;
				}

				if(isin1.IsNull && !isin2.IsNull) {
					return false;
				}

				if(!isin1.IsNull && isin2.IsNull) {
					return false;
				}

				if((isin1.IsNull && isin2.IsNull)
				|| (isin1.IsNull && isin2 == null)
				|| (isin1 == null && isin2.IsNull)) {
					return true;
				}

				if(isin1.CountryPart == isin2.CountryPart
				&& isin1.NsinPart == isin2.NsinPart
				&& isin1.CheckNumber == isin2.CheckNumber) {
					return true;
				}
				else {
					return false;
				}
			}
			catch(Exception) {
				return false;
			}
		}

		/// <summary>
		/// Checks if the first ISIN object does not equal the second ISIN object
		/// </summary>
		/// <param name="isin1">The first ISIN object.</param>
		/// <param name="isin2">The second ISIN object.</param>
		/// <returns>Returns a value indicating if the first ISIN object does not equal the second ISIN object.</returns>
		public static bool operator !=(ISIN isin1, ISIN isin2) {
			try {
				if(object.ReferenceEquals(isin1, isin2)) {
					return false;
				}

				if(((object)isin1 == null) || ((object)isin2 == null)) {
					return true;
				}

				if(isin1.IsNull && isin2.IsNull) {
					return false;
				}

				if(isin1.CountryPart != isin2.CountryPart
				|| isin1.NsinPart != isin2.NsinPart
				|| isin1.CheckNumber != isin2.CheckNumber) {
					return true;
				}
				else {
					return false;
				}
			}
			catch(Exception) {
				return false;
			}
		}

		/// <summary>
		/// Checks the internal parts of the ISIN for special characters
		/// </summary>
		/// <returns>Returns a value indicating if the internal parts of the ISIN contains special characters.</returns>
		public bool ContainsSpecialChars() {
			return ContainsSpecialChars(this._countryPart, this._nsinPart);
		}

		#region Static Members

		/// <summary>
		/// Checks if a string is a ISIN
		/// </summary>
		/// <param name="value">A string that could contain a ISIN.</param>
		/// <returns>Returns a value indicating if the string containing a ISIN.</returns>
		public static bool IsISIN(string value) {
			bool result = false;

			if(value != null
			&& (value.Length == 11 || value.Length == 12) 
			&& value.Substring(0, 2).IsAlpha()) {
				try {
					if(value.Length == 12
					&& ComputeChecksum(value) == 0) {
						result = true;

						if(value.Substring(11, 1).ToInt32() != ComputeChecksum(value.Substring(0, 11))) {
							result = false;
						}
					}
					else if(value.Length == 11) {
						ISIN isin;

						if(TryParse(value, out isin)) {
							if(isin.CountryPart != null
							&& isin.CountryPart.Length == 2
							&& isin.NsinPart != null
							&& isin.NsinPart.Length == 9
							&& isin.CheckNumber.HasValue
							&& !isin.ContainsSpecialChars()) {
								result = true;
							}
						}
						else {
							result = false;
						}
					}
				}
				catch(Exception) {
					result = false;
				}
			}

			return result;
		}

		/// <summary>
		/// Parse a string into a ISIN object and computes the check number if there is no check number
		/// </summary>
		/// <param name="value">A string containing a ISIN</param>
		/// <param name="isin">The value that could be set</param>
		/// <returns>Returns a value indicating if the string could be parsed to a ISIN.</returns>
		public static bool TryParse(string value, out ISIN isin) {
			try {
				isin = Parse(value);
				return true;
			}
			catch(Exception) {
				isin = null;
				return false;
			}
		}

		/// <summary>
		/// Parse a string into a ISIN object and computes the check number if there is no check number
		/// </summary>
		/// <param name="value">A string containing a ISIN</param>
		/// <returns>Returns a ISIN object.</returns>
		public static ISIN Parse(string value) {
			if(value == null) {
				throw new ArgumentNullException("The value parameter cannot be null!");
			}

			if(value.Length < 11
			|| value.Length > 12) {
				throw new ArgumentException("The length of the value parameter must be 11 (without checknumber) or 12 (with checknumber)!");
			}

			string countryPart = value.Substring(0, 2);
			string nsinPart = value.Substring(2, 9);
			int checkNumber = 0;

			if(ContainsSpecialChars(countryPart, nsinPart)) {
				throw new ArgumentException("The value contains incorrect characters and/or special characters!");
			}

			if(value.Length == 12) {
				checkNumber = value.Substring(11, 1).ToInt32();

				if(checkNumber != ComputeChecksum(value.Substring(0, 11))) {
					checkNumber = ComputeChecksum(value.Substring(0, 11));
				}
			}
			else {
				checkNumber = ComputeChecksum(value.Substring(0, 11));
			}

			return new ISIN(countryPart, nsinPart, checkNumber);
		}

		/// <summary>
		/// Computes a checknumber on base of the double-add-double method
		/// </summary>
		/// <param name="value">A value for the double-add-double method</param>
		/// <returns></returns>
		public static int ComputeChecksum(string value) {
			bool eachLetterForCalculation = true;
			bool checksuminValueIncluded = (value.Length == 11 ? false : true);

			return DoubleAddDouble.Compute(value, eachLetterForCalculation, checksuminValueIncluded);
		}

		/// <summary>
		/// Checks the two base parts of the ISIN for special characters
		/// </summary>
		/// <param name="country">The country part to check.</param>
		/// <param name="nsin">The nsin part to check.</param>
		/// <returns>Returns a value indicating if the two base parts of the ISIN contains special characters.</returns>
		public static bool ContainsSpecialChars(string country, string nsin) {
			foreach(char c in country) {
				if(DoubleAddDouble.GetLetterNumber(c.ToString()) == -1) {
					return true;
				}
			}

			foreach(char c in nsin) {
				if(!c.ToString().IsNumeric()
				&& DoubleAddDouble.GetLetterNumber(c.ToString()) == -1) {
					return true;
				}
			}

			return false;
		}

		#endregion
	}
}
